package jp.tkym.labs.store.sql;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import jp.tkym.labs.store.Beanstore;
import jp.tkym.labs.store.BeanstoreException;
import jp.tkym.labs.store.ormap.OrMapObject;
import jp.tkym.labs.store.ormap.OrMapObjectProvider;
import jp.tkym.labs.store.query.BeanQueryFilter;
import jp.tkym.labs.store.query.BeanQueryFilterOperator;
import jp.tkym.labs.util.beankey.BeanKey;
import jp.tkym.labs.util.beankey.BeanKeyGenerator;

public class SqliteBeanstore<B> implements Beanstore<B>{
	private Class<B> clsType; 
	private PreparedStatementExecutor executor;
	private String[] keyProperties;
	private SqliteCriteriaInterpreter<B> interpreter;
	private BeanKeyGenerator<B> keyGenerator;
	private OrMapObject orMapper;
	
	public SqliteBeanstore(Class<B> cls, PreparedStatementExecutor executor){
		clsType = cls;
		this.executor = executor;
		keyProperties = OrMapObjectProvider.getInstance().get(cls).keyProperties();
		interpreter = new SqliteCriteriaInterpreter<B>(cls);
		keyGenerator = new BeanKeyGenerator<B>(cls, keyProperties);
		orMapper = OrMapObjectProvider.getInstance().get(cls);
		
	}
	
	public SqliteBeanstore(Class<B> cls, Connection connetion){
		this(cls, new PreparedStatementExecutor(connetion));
	}
	
	boolean existsTable() throws BeanstoreException{
		try {
			String appendSql = " where TYPE = ? and NAME = ?";
			PreparedStatementQuery<SqliteMaster> query = 
				executor.nativeEntityQuery(SqliteMaster.class, appendSql , "table", orMapper.getObjectName());
			query.execute();
			if(query.asList().size() > 0) return true;
			return false;
		} catch (Exception e) {
			throw new BeanstoreException(e);
		}
	}
	
	void create()throws BeanstoreException{
		try {
			executor.create(clsType);
		} catch (SQLException e) {
			throw new BeanstoreException(e);
		}
	}

	void drop()throws BeanstoreException{
		try {
			executor.drop(clsType);
		} catch (SQLException e) {
			throw new BeanstoreException(e);
		}
	}
	
	void insert(B bean) throws BeanstoreException{
		try {
			executor.insert(bean);
		} catch (SQLException e) {
			throw new BeanstoreException(e);
		}
	}
	
	void update(B bean) throws BeanstoreException{
		try {
			executor.update(bean);
		} catch (SQLException e) {
			throw new BeanstoreException(e);
		}
	}
	
	void merge(B bean) throws BeanstoreException{
		if(contains(bean)){
			update(bean);
		}else{
			insert(bean);
		}
	}
	
	void delete(BeanKey key) throws BeanstoreException{
		try {
			executor.delete(clsType, key);
		} catch (SQLException e) {
			throw new BeanstoreException(e);
		}
	}
	
	void delete(B bean) throws BeanstoreException{
		try {
			executor.delete(bean);
		} catch (SQLException e) {
			throw new BeanstoreException(e);
		}
	}
	
	public void ensure() throws BeanstoreException{
		if(!existsTable()) create();
	}
	
	@Override
	public void put(B bean) throws BeanstoreException {
		merge(bean);
	}

	@Override
	public void remove(B bean) throws BeanstoreException {
		delete(bean);
	}
	
	private boolean contains(B bean) throws BeanstoreException {
		BeanKey key = keyGenerator.generate(bean);
		return contains(key);
	}
	
	@Override
	public boolean contains(BeanKey key) throws BeanstoreException {
		return keyset(key).contains(key);
	}
	
	Set<BeanKey> keyset(BeanKey key) throws BeanstoreException {
		String sql = interpreter.whereStatement(keyFilter(key.values()));
		try {
			PreparedStatementQuery<B> query = executor.nativeKeyQuery(clsType, sql, key.values());
			query.execute();
			return query.asKeySet();
		} catch (SQLException e) {
			throw new BeanstoreException(e);
		}
	}
	
	List<BeanQueryFilter> keyFilter(Object[] values){
		List<BeanQueryFilter> list = new ArrayList<BeanQueryFilter>(keyProperties.length);
		if(keyProperties.length == values.length){
			for(int i=0; i<keyProperties.length; i++){
				list.add(equalsTo(keyProperties[i], values[i]));
			}
			return list;
		}else{
			String message = "key Properties size of " + clsType.getName()+
			" is " + keyProperties.length ;
			throw new IllegalArgumentException(message);
		}
	}
	
	private BeanQueryFilter equalsTo(String property, Object value){
		BeanQueryFilter filter = 
			new BeanQueryFilter(property, 
					BeanQueryFilterOperator.EQUAL,
					value);
		return filter;
	}
	
	@Override
	public B get(BeanKey key) throws BeanstoreException {
		String sql = interpreter.whereStatement(keyFilter(key.values()));
		try {
			PreparedStatementQuery<B> query = executor.nativeEntityQuery(clsType, sql, key.values());
			query.execute();
			return query.asList().get(0);
		} catch (SQLException e) {
			throw new BeanstoreException(e);
		}
	}
	
}